<?php

defined('SYSPATH') or die('Access error!');

abstract class ACL_Core {

	// 对于所有类型的通配符
	const WILDCARD = '*';

	/**
	 * @var  array 目前的角色/资源/权限匹配
	 */
	protected $command      = array();

	protected $_roles       = array();

	protected $_resources   = array();

	protected $_permissions = array();

	/**
	 * 增加一个角色.
	 *     // 例如添加一个guest
	 *     $acl->role('guest');
	 *     // 添加一个member 继承guest
	 *     $acl->role('member', 'guest');
	 *     // 添加一个owner继承guest  member
	 *     $acl->role('owner', array('guest','member'));
	 */
	public function add_role($role, $parents = NULL)
	{
		$role = $role instanceof Acl_Role_Interface
			? $role->get_role_id()
			: (string) $role;

		if ( ! is_array($parents))
		{
			if ($parents === NULL)
			{
				$parents = array();
			}
			else
			{
				$parents = array($parents);
			}
		}

		$this->_roles[$role] = $parents;

		return $this;
	}

	/**
	 * 添加一个资源
	 *     $acl->resource('users');
	 *     $acl->resource('latest', 'news');
	 */
	public function add_resource($resource, $parents = NULL)
	{
		$resource = $resource instanceof Acl_Resource_Interface
			? $resource->get_resource_id()
			: (string) $resource;

		if ( ! is_array($parents))
		{
			if ( ! $parents)
			{
				$parents = array();
			}
			else
			{
				$parents = array($parents);
			}
		}

		$this->_resources[$resource] = $parents;

		return $this;
	}

	/**
	 * 获取角色包括父类的权限
	 *     $roles = $acl->roles('member');
	 */
	public function roles($name)
	{
		$roles = array($name);

		if ( isset($this->_roles[$name]))
		{
			foreach ( $this->_roles[$name] as $role)
			{
				$roles = array_merge($this->roles($role), $roles);
			}
		}

		return $roles;
	}

	/**
	 * 获取资源 包括父类的
	 *     $roles = $acl->resources('news');
	 */
	protected function resources($name)
	{
		$resources = array($name);

		if ( isset($this->_resources[$name]))
		{
			foreach ( $this->_resources[$name] as $resource)
			{
				$resources = array_merge($this->resources($resource), $resources);
			}
		}
		return $resources;
	}

	/**
	 * 为某个角色添加权限.
	 *     // 允许guest访问news
	 *     $acl->allow('guest', 'news', 'view');
	 *     // 允许member 给news留言
	 *     $acl->allow('member', 'news', 'comment');
	 *     // 允许admin操作所有选择
	 *     $acl->allow('admin');
	 */
	public function allow($roles = NULL, $resources = NULL, $privileges = NULL, Acl_Assert_Interface $assertion = NULL)
	{
		return $this->add_rule(TRUE, $roles, $resources, $privileges, $assertion);
	}

	/**
	 * 禁止某个操作
	 *     // 禁止member编辑news
	 *     $acl->deny('member', 'news', 'edit');
	 */
	public function deny($roles = NULL, $resources = NULL, $privileges = NULL, Acl_Assert_Interface $assertion = NULL)
	{
		return $this->add_rule(FALSE, $roles, $resources, $privileges, $assertion);
	}

	/**
	 * 添加一个允许的一个角色，设置资源，特权，访问类型（允许，拒绝）。
	 *     // 让admin可以操作任何事情
	 *     $acl->add_rule(TRUE, 'admin', NULL, NULL);
	 */
	private function add_rule($allow, $roles, $resources, $privileges, $assertion)
	{
		$entities = array('roles', 'resources', 'privileges');

		foreach ($entities as $entity)
		{
			if ($$entity)
			{
				if ( ! is_array($$entity))
				{
					$$entity = array($$entity);
				}
			}
			else
			{
				$$entity = array(ACL::WILDCARD);
			}
		}

		$allow = array(
			'allow' => (bool) $allow
		);

		if ( $assertion)
		{
			$allow['assert'] = $assertion;
		}

		foreach ( $privileges as $privilege)
		{
			foreach ( $resources as $resource)
			{
				$resource = $resource instanceof Acl_Resource_Interface
					? $resource->get_resource_id()
					: (string) $resource;

				foreach ( $roles as $role)
				{
					$role = $role instanceof Acl_Role_Interface
						? $role->get_role_id()
						: (string) $role;

					$this->_permissions[$role][$resource][$privilege] = $allow; 
				}
			}
		}

		return $this;
	}

	/**
	 * 检查是否有权限
	 *     例如：
	 *     $acl->is_allowed('guest', 'news', 'comment');
	 *     $acl->is_allowed('member', 'news', 'commment');
	 */
	public function is_allowed($role = NULL, $resource = NULL, $privilege = NULL)
	{
		$roles = is_array($role)
			? $role
			: array($role);

		foreach ( $roles as $role)
		{
			$this->command = array(
				'role'      => $role,
				'resource'  => $resource,
				'privilege' => $privilege
			);

			if ( $role)
			{
				$role = $role instanceof Acl_Role_Interface
					? $role->get_role_id()
					: (string) $role;
			}

			$role_array = is_array($role)
				? $role
				: array($role);

			if ( $resource)
			{
				$resource = $resource instanceof Acl_Resource_Interface
					? $resource->get_resource_id()
					: (string) $resource;
			}

			foreach ( $role_array as $role)
			{
				$allowed = $this->match($role, $resource, $privilege);

				if ( $allowed === TRUE && in_array(NULL, $this->command))
				{
	
					$_roles = isset($role)
						? array($role)
						: array_keys($this->_roles);
	
					$_resources = isset($resource)
						? array($resource)
						: array_keys($this->_resources);
	
					if ( ! isset($privilege))
					{
						$_privileges = array();
	
						foreach ( $this->_permissions as $res)
						{
							foreach ( $res as $privs)
							{
								$_privileges = array_merge($_privileges, array_keys($privs));
							}
						}
	
						$_privileges = array_diff($_privileges, array(ACL::WILDCARD));
					}
					else
					{
						$_privileges = array($privilege);
					}
	
					if ( count($_roles) === 0)      $_roles      = array(ACL::WILDCARD);
					if ( count($_resources) === 0)  $_resources  = array(ACL::WILDCARD);
					if ( count($_privileges) === 0) $_privileges = array(ACL::WILDCARD);
	
					foreach ( $_roles as $_ro)
					{
						foreach ( $_resources as $_re)
						{
							foreach ( $_privileges as $_pr)
							{
								if ( ! $this->match($_ro, $_re, $_pr))
								{
									return FALSE;
								}
							}
						}
					}
				}

				if ( $allowed === TRUE)
				{
					return $allowed;
				}
			}
		}

		return FALSE;
	}

	/**
	 * 用于匹配是否有权限操作
	 *
	 */
	protected function match($role, $resource, $privilege)
	{
		$roles = $resources = $privileges = array(ACL::WILDCARD);

		if ( $role)
		{
			$roles = array_merge($roles, $this->roles($role));
		}

		if ( $resource)
		{
			$resources = array_merge($resources, $this->resources($resource));
		}

		if ( $privilege)
		{
			$privileges[] = $privilege;
		}

		$allowed    = NULL;
		$roles      = array_reverse($roles);
		$resources  = array_reverse($resources);
		$privileges = array_reverse($privileges);

		foreach ( $roles as $roid => $_role)
		{
			foreach ( $resources as $reid => $_resource)
			{
				foreach ( $privileges as $prid => $_privilege)
				{
					if ( isset($this->_permissions[$_role][$_resource][$_privilege]))
					{
						$match = $this->_permissions[$_role][$_resource][$_privilege];

						if ( ! isset($match['assert']) || $match['assert']->assert($this, $this->command['role'], $this->command['resource'], $this->command['privilege']))
						{
							$allowed = $match['allow'];
							break 3;
						}
					}
				}
			}
		}

		return $allowed === TRUE;
	}

	public function __sleep()
	{
		return array('_roles','_resources','_permissions'); 
	}
}